package top.ninwoo.utils.service.impl;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.ninwoo.utils.entity.ChainEntity;
import top.ninwoo.utils.entity.ChainType;
import top.ninwoo.utils.entity.TableType;
import top.ninwoo.utils.service.IptablesService;
import top.ninwoo.utils.util.IptablesUtils;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Author joliu
 * @Description 这个服务的实现类，并没有采用定时线程进行更新，
 *  采用了一种类似缓存的实现方案，难点要保持每次操作和Dockeriptables操作的一致性
 *  TODO: 未测试
 * @Date　Create in 上午10:08 2019/10/30
 */
@Service
public class IptablesServiceImpl implements IptablesService {
    // 以容器为单位维护着每个容器的iptables
    // 这里里边只是维护了filter的表结构
    private ConcurrentHashMap<String, Map<String, List<ChainEntity>>> allIptables
            = new ConcurrentHashMap<>();
    // 维护nat表结构
    private ConcurrentHashMap<String, Map<String, List<ChainEntity>>> natIpatbles
            = new ConcurrentHashMap<>();
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    @Autowired
    IptablesUtils iptablesUtils;

    // 这个服务中不需要进行任务的定时更新，默认其他人无法访问docker容器中的配置

    /**
     * 根据容器Id查询全部的iptables信息,默认查询的是Filter
     *  这里的容器id，要求必须存在，需要在调用前进行验证
     * @param containerId
     * @return
     */
    @Override
    public Map<String, List<ChainEntity>> getIptablesByContainerId(String containerId) {
        return getIpatblesByContainerId(containerId, "filter");
    }

    /**
     * 查看iptables信息
     * @param containerId
     * @param type
     * @return
     */
    public Map<String, List<ChainEntity>> getIpatblesByContainerId(String containerId, String type) {
        Map<String, List<ChainEntity>> res = null;
        try {
            rwLock.readLock().lock();
            switch (type) {
                case "filter":
                    res = allIptables.get(containerId);
                    break;
                case "nat":
                    res = natIpatbles.get(containerId);
                    break;
            }
        } finally {
            rwLock.readLock().unlock();
        }
        return res;
    }

    /**
     * 根据容器Id查询iptables信息
     * @param containerId 容器id
     * @param chainType chain的类型
     * @return
     */
    @Override
    public List<ChainEntity> getIptablesByContainerId(String containerId, ChainType chainType) {
        return getIptablesByContainerId(containerId, chainType, "filter");
    }

    @Override
    public List<ChainEntity> getIptablesByContainerId(String containerId, ChainType chainType, String type) {
        List<ChainEntity> res = null;
        checkCache(containerId);
        try {
            rwLock.readLock().lock();
            switch (type) {
                case "filter":
                    res = allIptables.get(containerId).get(chainType.toString());
                    break;
                case "nat":
                    res = natIpatbles.get(containerId).get(chainType.toString());
                    break;
            }
        } finally {
            rwLock.readLock().unlock();
        }
        return res;
    }

    @Override
    public ChainEntity getIptablesByContainerId(String containerId, ChainType chainType, int lineNumber) {
        checkCache(containerId);
        List<ChainEntity> chains = getIptablesByContainerId(containerId, chainType);
        if(chains.size() < lineNumber) {
            return null;
        }
        return chains.get(lineNumber);
    }

    /**
     * 校验缓存是否存在，如果不存在的话，则创建一次查询，并添加到缓存中
     * @param containerId
     */
    @Override
    public void checkCache(String containerId) {
        if(!allIptables.containsKey(containerId)) {
            // 如果查询不到，证明该容器之前没有被操作过，调用底层命令，进行一次查询
            synchronized (allIptables) {
                if(!allIptables.containsKey(containerId)) {
                    Map<String, List<ChainEntity>> containerIptables = iptablesUtils.showIptablesDetail(containerId);

                    allIptables.put(containerId, containerIptables);
                }
            }
        }
        if(!natIpatbles.containsKey(containerId)) {
            synchronized (natIpatbles) {
                if(!natIpatbles.containsKey(containerId)) {
                    Map<String, List<ChainEntity>> nat = iptablesUtils.showIptablesDetail(containerId, "nat");
                    natIpatbles.put(containerId, nat);
                }
            }
        }
    }

    /**
     * 使用chainentity删除对应的iptable
     * @param containerId
     * @param chainEntity
     */
    @Override
    public void deleteIptable(String containerId, ChainEntity chainEntity) {
        checkCache(containerId);
        try {
            rwLock.writeLock().lock();
            iptablesUtils.delIptable(containerId, chainEntity.getType(), chainEntity.getSource(), chainEntity.getDestination(), chainEntity.getTarget());
            // 更新一下docker的iptables现状
            deleteIptablesFromList(containerId, chainEntity);
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    /**
     * 更新iptables容器,有安全问题，如果有其他人插入，这边删除是有安全问题的 TODO:
     * @param containerId
     * @param chainEntity
     */
    @Override
    public void deleteIptablesFromList(String containerId, ChainEntity chainEntity) {
        List<ChainEntity> chainEntities = allIptables.get(containerId).get(chainEntity.getType().toString());
        for (int i = chainEntity.getId(); i < chainEntities.size(); i++) {
            chainEntities.get(i).setId(chainEntities.get(i).getId() - 1);
        }
        chainEntities.remove(chainEntity.getId() - 1);
    }

    /**
     * 通过行号进行删除
     * @param containerId
     * @param type
     * @param lineNumber
     */
    @Override
    public void deleteIptableById(String containerId, ChainType type, int lineNumber) {
        checkCache(containerId);
        // 删除第lineNumber个条目
        try {
            rwLock.writeLock().lock();
            iptablesUtils.delIptable(containerId, TableType.filter, type, lineNumber);
            allIptables.get(containerId).get(type.toString()).remove(lineNumber - 1);
        } finally {
            rwLock.writeLock().unlock();
        }
    }


    /**
     * 插入一个iptable, 插到前边
     * @param containerId
     * @param chainEntity
     * @return
     */
    public ChainEntity insertIptable(String containerId, ChainEntity chainEntity) {
        checkCache(containerId);

        try {
            rwLock.writeLock().lock();
            // 先写入Docker
            iptablesUtils.addIptable(containerId, "insert", chainEntity.getType(),
                    chainEntity.getSource(), chainEntity.getDestination(), chainEntity.getTarget());
            // 再写入all iptables
            chainEntity.setId(1);
            insertIntoIptables(containerId, chainEntity);
        } finally {
            rwLock.writeLock().unlock();
        }
        return chainEntity;
    }

    /**
     * 插入到iptables
     * @param containerId
     * @param chainEntity
     */
    private void insertIntoIptables(String containerId, ChainEntity chainEntity) {
        List<ChainEntity> chainEntities = allIptables.get(containerId).get(chainEntity.getType().toString());
        chainEntities.forEach(c -> {c.setId(c.getId() + 1);});
        chainEntities.add(0, chainEntity);
    }

    /**
     * 在后边追加一个iptable，插到后边
     * @param containerId
     * @param chainEntity
     * @return
     */
    public ChainEntity appendIptable(String containerId, ChainEntity chainEntity) {
        checkCache(containerId);

        try {
            rwLock.writeLock().lock();
            // 先写入Docker
            iptablesUtils.addIptable(containerId, "append", chainEntity.getType(),
                    chainEntity.getSource(), chainEntity.getDestination(), chainEntity.getTarget());
            // 再添加alliptables最后一个
            List<ChainEntity> chainEntities = allIptables.get(containerId).get(chainEntity.getType().toString());
            chainEntity.setId(chainEntities.size());
            chainEntities.add(chainEntity);
        } finally {
            rwLock.writeLock().unlock();
        }
        return chainEntity;
    }

    /**
     * 转发流量到其他容器
     * @param containerId 执行转发操作的容器id
     * @param selfIp 执行转发操作的容器ip
     * @param selfPort 执行转发操作的容器端口
     * @param toIp 转发的目的ip地址
     * @param toPort 转发的目的端口
     * @return
     */
    @Override
    public boolean forwardTraffic(String containerId, String selfIp, int selfPort, String toIp, int toPort) {
        /**
         *
         Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
         pkts bytes target     prot opt in     out     source               destination
            1    60 DNAT       tcp  --  *      *       0.0.0.0/0            172.17.0.3           tcp dpt:5001 to:172.17.0.4:5001

         Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
         pkts bytes target     prot opt in     out     source               destination

         Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
         pkts bytes target     prot opt in     out     source               destination
         1    60 SNAT       tcp  --  *      *       0.0.0.0/0            172.17.0.4           tcp dpt:5001 to:172.17.0.3

         Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
         pkts bytes target     prot opt in     out     source               destination
         */
        // TODO: 这个实现借口还需要进一步的封装，这里采用一种不是很合理的结构创建
        // 正向
        selfIp = selfIp.substring(0, selfIp.length() - 3);
        toIp = toIp.substring(0, toIp.length() - 3);
        String preRouting = "iptables -t nat -A PREROUTING -d " + selfIp + " -p tcp --dport "
                + selfPort + " -j DNAT --to-destination " + toIp + ":" + toPort;
        // 回路
        String postRouting = "iptables -t nat -A POSTROUTING -d " + toIp + " -p tcp --dport " + toPort + " -j SNAT --to-source " + selfIp;

        // 下发iptables
        String s = iptablesUtils.supperiseCmd(containerId, preRouting);
        if (!"".equals(s)) {
            System.out.println(s);
            return false;
        }
        // 这里构建一个iptables
        ChainEntity chainEntity = new ChainEntity.Builder()
                .type(ChainType.PREROUTING)
                .target("DNAT")
                .prot("tcp")
                .opt("--")
                .in("*")
                .out("*")
                .source("0.0.0.0/0")
                .destination(selfIp)
                .more("tcp dpt:" + selfIp + " to:" + toIp + ":" + toPort)
                .build();
        checkCache(containerId);
        List<ChainEntity> chainEntities = natIpatbles.get(containerId).get(chainEntity.getType().toString());
        chainEntity.setId(chainEntities.size());
        chainEntities.add(chainEntity);

        // 插入到natIptables中
        s = iptablesUtils.supperiseCmd(containerId, postRouting);
        if(!"".equals(s)) {
            System.out.println(s);
            return false;
        }
        // 构建一个iptables
        // 插入到natIptables中
        //pkts bytes target     prot opt in     out     source               destination
        //         1    60 SNAT       tcp  --  *      *       0.0.0.0/0            172.17.0.4           tcp dpt:5001 to:172.17.0.3
        ChainEntity chainEntity2 = new ChainEntity.Builder()
                .type(ChainType.POSTROUTING)
                .target("DNAT")
                .prot("tcp")
                .opt("--")
                .in("*")
                .out("*")
                .source("0.0.0.0/0")
                .destination(selfIp)
                .more("tcp dpt:" + selfIp + " to:" + toIp + ":" + toPort)
                .build();

        List<ChainEntity> chainEntities2 = natIpatbles.get(containerId).get(chainEntity2.getType().toString());
        chainEntity2.setId(chainEntities2.size());
        chainEntities2.add(chainEntity2);

        return true;
    }

    @Override
    public void dropTraffic(String containerId, String sourceIp, String destinationIp) {
        String s = iptablesUtils.addIptable(containerId, "append", ChainType.INPUT, sourceIp, destinationIp, "DROP");
        if(!s.equals("")) {
            throw new RuntimeException("container下发流表出错:" + containerId);
        }
    }

    @Override
    public void cancelDropTraffic(String containerId, String souceIp, String destinationIp) {
        iptablesUtils.delIptable(containerId, ChainType.INPUT, souceIp, destinationIp, "DROP");
    }
}
